//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>
#define _ERRNO_
#include <errno.h>

#include "define.h"
#include "serialio.h"
#include "control.h"

#if defined(_gingko)
#define BT_POWER_ON {if(m_irq==21)NotifyByIsr(EVENT_BUS_POWER, 0x2<<16, NULL);}
#else
#define BT_POWER_ON
#endif

//
// Serial
//
class Serial : public CDeviceDriver
{
public:
    CARAPI Read(
        /* [in] */ Int64 u64Offset,
        /* [in] */ Int32 bytesToRead,
        /* [out] */ MemoryBuf * pBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Write(
        /* [in] */ Int64 u64Offset,
        /* [in] */ const MemoryBuf & buffer,
        /* [out] */ Int32 * pBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent);

    CARAPI Control(
        /* [in] */ Handle32 nControlCode,
        /* [in] */ const MemoryBuf & inBuffer,
        /* [out] */ MemoryBuf * pOutBuffer,
        /* [out] */ IEvent * * ppCompletionEvent);

    virtual void Dispose();

public:
    Serial(ioport_t iobase, irq_t irq)
    {
        m_iobase = iobase;
        m_irq = irq;
    }

    void Isr();

    ECode Initialize();

public:
    ioport_t        m_iobase;
    irq_t           m_irq;
    devtab_entry    m_devtab;
};

ECode Serial::Read(
    /* [in] */ Int64 u64Offset,
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (pBuffer == NULL ||
        (int)bytesToRead > pBuffer->GetCapacity() ||
        0 == bytesToRead) {
        return E_INVALID_ARGUMENT;
    }
    BT_POWER_ON
    ECode  ec;

    SERIAL_ErrNo eno =
        g_serial_devio.read(
            &m_devtab,
            (char *)pBuffer->GetPayload(),
            &bytesToRead);
    if (eno == ENOERR) {
        pBuffer->SetUsed(bytesToRead);
        ec = NOERROR;
    }
    else if (eno == -ETIMEDOUT) {
        ec = E_TIMED_OUT;
    }
    else if (eno == -EINVAL) {
        ec = E_INVALID_ARGUMENT;
    }
    else if (eno == -EAGAIN) {
        ec = E_INTERRUPTED;
    }
    else {
        ec = E_IO;
    }

    return ec;
}

ECode Serial::Write(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (buffer.IsNullOrEmpty()
        || NULL == pBytesWritten) {
        return E_INVALID_ARGUMENT;
    }
    BT_POWER_ON
    ECode ec;
    *pBytesWritten = buffer.GetUsed();
    SERIAL_ErrNo eno =
        g_serial_devio.write(
            &m_devtab,
            (char *)buffer.GetPayload(),
            pBytesWritten);
    if (eno == ENOERR) {
        ec = NOERROR;
    }
    else if (eno == -ETIMEDOUT) {
        ec = E_TIMED_OUT;
    }
    else if (eno == -EINVAL) {
        ec = E_INVALID_ARGUMENT;
    }
    else if (eno == -EAGAIN) {
        ec = E_INTERRUPTED;
    }
    else {
        ec = E_IO;
    }

    return ec;
}

ECode Serial::Control(
    /* [in] */ Handle32 nControlCode,
    /* [in] */ const MemoryBuf & inBuffer,
    /* [out] */ MemoryBuf * pOutBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    ECode ec;
    SERIAL_ErrNo eno;
    Int32 uNumberOfBytes;
    BT_POWER_ON
    switch (nControlCode & SERIALCONTROL_OPERATIONMASK) {
        case SERIALCONTROL_GETCONFIG: {
            void * outBufPtr = NULL;
            if (pOutBuffer == NULL) { uNumberOfBytes = 0; }
            else {
                outBufPtr = pOutBuffer->GetPayload();
                uNumberOfBytes = (UInt32) pOutBuffer->GetCapacity();
            }
            eno = g_serial_devio.get_config(
                &m_devtab,
                nControlCode & SERIALCONTROL_OBJECTMASK,
                outBufPtr,
                &uNumberOfBytes);
            if (eno == ENOERR) {
                if (pOutBuffer && pOutBuffer->GetCapacity()) {
                    pOutBuffer->SetUsed(uNumberOfBytes);
                }
                ec = NOERROR;
            }
            else if (eno == -ETIMEDOUT) {
                ec = E_TIMED_OUT;
            }
            else if (eno == -EINVAL) {
                ec = E_INVALID_ARGUMENT;
            }
            else if (eno == -EAGAIN) {
                ec = E_INTERRUPTED;
            }
            else {
                ec = E_IO;
            }
        }
            return ec;

        case SERIALCONTROL_SETCONFIG:
            uNumberOfBytes = (UInt32) inBuffer.GetCapacity();
            eno = g_serial_devio.set_config(
                &m_devtab,
                nControlCode & SERIALCONTROL_OBJECTMASK,
                (char *) inBuffer.GetPayload(),
                &uNumberOfBytes);
            if (eno == ENOERR) {
                ec = NOERROR;
            }
            else if (eno == -ETIMEDOUT) {
                ec = E_TIMED_OUT;
            }
            else if (eno == -EINVAL) {
                ec = E_INVALID_ARGUMENT;
            }
            else if (eno == -EAGAIN) {
                ec = E_INTERRUPTED;
            }
            else {
                ec = E_IO;
            }

            return ec;

        case SERIALCONTROL_WAITEVENT:{
            if (pOutBuffer == NULL) { uNumberOfBytes = 0; }
            else { uNumberOfBytes = (UInt32) pOutBuffer->GetCapacity(); }

            int *eventid;
            if (uNumberOfBytes < (Int32)sizeof(int)) {
                return E_INVALID_ARGUMENT;
            }
            eventid = (int *)pOutBuffer->GetPayload();
            eno = g_serial_devio.wait_event(
                &m_devtab,
                eventid);
            if (eno == ENOERR) {
                pOutBuffer->SetUsed(sizeof(int));
                ec = NOERROR;
            }
            else if (eno == -ETIMEDOUT) {
                ec = E_TIMED_OUT;
            }
            else if (eno == -EINVAL) {
                ec = E_INVALID_ARGUMENT;
            }
            else if (eno == -EAGAIN) {
                ec = E_INTERRUPTED;
            }
            else {
                ec = E_IO;
            }

            return ec;
        }

        default:
            return E_INVALID_ARGUMENT;
    }

    switch(nControlCode & ~SERIALCONTROL_OPERATIONMASK) {
        case 1000://Device Suspend
            //Add your code here
            ec = NOERROR;
            break;
        case 1001://Device Resume
            //Add your code here
            ec = NOERROR;
            break;
        case 1002://Device Wakeup
            //Add your code here
            ec = NOERROR;
            break;
        case 1003://Device Powerdown
            //Add your code here
            ec = NOERROR;
            break;
        default:
            ec = E_INVALID_ARGUMENT;
            break;
    }

    return ec;
}

void Serial::Dispose()
{
    delete this;
}

INLINE void Serial::Isr()
{
    serial_channel *channel =
        (serial_channel *) m_devtab.priv;
    channel->funs->isr(channel);
}

STATIC void CDECL SerialIsr(
    irq_t irq, void *pvDevice, InterruptContext *pContext)
{
    assert(pvDevice);
    ((Serial *)pvDevice)->Isr();
}

IDeviceDriver* g_pPppDriver;

ECode Serial::Initialize()
{
    if (!serial_device_entry(&m_devtab, m_iobase, m_irq)) {
        return E_IO;
    }

    DzRegisterIsr(m_irq, IPL2, &SerialIsr, this);

    return NOERROR;
}

EXTERN IDeviceDriver * CDECL CreateSerial(uint_t uDeviceNo, void *pvParameter)
{
    ioport_t iobase;
    irq_t irq;

    if (uDeviceNo <= g_port_tbl.num_of_port) {
        iobase = g_port_tbl.port[uDeviceNo - 1].iobase;
        irq = g_port_tbl.port[uDeviceNo - 1].irq;
    }
    else {
        return NULL;
    }

    Serial *pSerial = new Serial(iobase, irq);
    if (NULL == pSerial) return NULL;

    ECode ec = pSerial->Initialize();
    if (FAILED(ec)) {
        delete pSerial;
        return NULL;
    }

    pSerial->AddRef();
    if (uDeviceNo == 2) {
        g_pPppDriver = pSerial;

#if defined(_techfaith) //for TF: Disable serial2 irq to use console
        DzDisableIrq(irq);
#endif //_techfaith

    }

#if defined(_gingko) //for GK: Disable serial3 irq to use console
    if (uDeviceNo == 3) {
        DzDisableIrq(irq);
    }
#endif //_gingko

#if defined(_c28) //for GK: Disable serial3 irq to use console
    if (uDeviceNo == 3) {
        DzDisableIrq(irq);
    }
#endif //_c28

    return pSerial;
}
